/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.agent;

import hermesV2.BasicServices;
import hermesV2.Message;
import hermesV2.PlaceAddress;

import hermesV2.basicServices.communication.SendFailedException;

import hermesV2.util.HermesLogger;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


/**
 * The agent class represents the main class to develop agents. It has some
 * features to send, receive message, to move or to clone it self. It is
 * extened into to different classes, the User agent and to Service agent. In
 * the Agent class there are features common to a general agent.
 *
 * @author Diego Bonura
 */
public abstract class Agent implements Serializable {
    /**
	 * 
	 */
	private static final long serialVersionUID = 4558930058991510065L;

	/**
     * It represents that the agent is in the init state, so in the first
     * esecution.
     */
    public static final int INIT = 0;

    /**
     * The agent identificator, every agent must have a different
     * identificator.
     */
    private Identificator id;


    /**
     * The reference to every basic services currentlly avaiable to the current
     * place.
     */
    protected transient BasicServices basicServices;

    /** The name of the agent, could be null. */
    private String agentName;

    /**
     * Creates a new Agent object.
     *
     * @param agentName The name of the agent.
     */
    public Agent(String agentName) {
        this.agentName = agentName;
    }
    
    /**
     * Sets the agent name.
     *
     * @param agentName The name of the agent.
     */
    public void setAgentName(String agentName) {
        this.agentName = agentName;
    }

    /**
     * Executes a method .
     *
     * @param methodName the name of the method to execute.
     * @param paramTypeList the method argument parameter list.
     * @param argList the method argument list.
     *
     * @see java.lang.reflect.Method
     * 
     * @throws InvocationTargetException The method to call is not correct.
     * @throws IllegalAccessException The executing method does not have access to the definition of the specified class.
     * @throws NoSuchMethodException When the callback method cannot be found.
     * @throws ThreadDeath Used to stop the execution of the method.
    
     */
    final void executeMethod(String methodName, Class<?>[] paramTypeList, Object[] argList) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ThreadDeath {
        if (methodName == null) {
            HermesLogger.log(HermesLogger.WARNING, "Agent:" + id + " Callback/Cloneback not defined");
        } else {
            try {
                Method method = this.getClass().getMethod(methodName, paramTypeList);

                try {
                    method.invoke(this, argList);
                } catch (InvocationTargetException ite) {
                    if (ite.getCause().getClass() == ThreadDeath.class) {
                        throw new ThreadDeath();
                    } 
                    throw ite;
                    
                } catch (IllegalAccessException iae) {
                    throw iae;
                }
            } catch (NoSuchMethodException nsme) {
                throw nsme;
            }
        }
    }

    /**
     * This methid is automatic called every time an agent starts or restarts
     * after a migration. Sets the current basic services avaiable in the
     * current place.
     *
     * @param basicServices The container of every basic services present in
     *        the current platform.
     */
    final public void setBasicServices(BasicServices basicServices) {
        this.basicServices = basicServices;
    }

    /**
     * The place uses this method to set automatically a different
     * identificator to every agent.
     *
     * @param id The agent identificator generated by the place (Id component).
     */
    final public void setIdentificator(Identificator id) {
        this.id = id;
    }
    
    /**
     * Send a message to a UserAgent, first checks if the Agent Receiver is a
     * correct user agent then if there is the communication component. Then
     * uses the interface Send to send  the message.
     * @param <T>
     *
     * @param message The message to send.
     *
     * @throws CommunicationException Some problems with the communication.
     */
    final public <T> void sendMessageToUserAgent(Message<T> message) throws CommunicationException {
         //controlls if the receiver is a UserAgent
        
        if (!message.getReceiverAgentId().isServiceAgent()) {
            //checks if there is the possibility to communicate
            if (basicServices.getSendInterface() != null) {
                try {
                    basicServices.getSendInterface().sendMessageToUserAgent(message);
                } catch (SendFailedException sfe) {
                    throw new CommunicationException(sfe.getMessage());
                }
            } else {
                throw new CommunicationException("Agent:There is no Communication Component");
            }
        } else {
            throw new CommunicationException("Agent:The receiver is a ServiceAgent");
        }
    }

    /**
     * Send a message to a local ServiceAgent, first checks if the Agent
     * Receiver is a correct service agent then if there is the communication
     * component. Then uses the interface Send to send  the message.
     * @param <T>
     *
     * @param message The message to send.
     *
     * @throws CommunicationException Some problems with the communication.
     */
    final public <T> void sendMessageToServiceAgent(Message<T> message) throws CommunicationException {
        //controlls if the receiver is a ServiceAgent
        if (message.getReceiverAgentId() == null) {
            throw new CommunicationException("Agent: receiver Identificator must be not null");
        }

        if (message.getReceiverAgentId().isServiceAgent()) {
            //checks if there is the possibility to communicate
            if (basicServices.getSendInterface() != null) {
                try {
                    if (basicServices.getSecurityInterface() == null) {
                        basicServices.getSendInterface().sendMessageToServiceAgent(message);
                        HermesLogger.log(HermesLogger.DEBUG, "Agent:" + id + "No security component, message passed to service agent without protection");
                    } else {
                        boolean checkResult = basicServices.getSecurityInterface().checkLink(this.getIdentificator(), message.getReceiverAgentId());
                        if (checkResult) {
                            basicServices.getSendInterface().sendMessageToServiceAgent(message);
                        } else {
                            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No permission to communicate with the service agent");
                        }
                    }
                } catch (SendFailedException sfe) {
                    throw new CommunicationException(sfe.getMessage());
                }
            } else {
                throw new CommunicationException("Agent:There is no Communication Component");
            }
        } else {
            throw new CommunicationException("Agent:The receiver is a UserAgent");
        }
    }

    /**
     * Returns a message waiting to be received by the specific agent. If there
     * is no  messages waiting the method blocks the agent excecution until a
     * new message is avaiable. If the agent is not registered to the
     * communication component (see reception()) the method returns null.
     * @param <T>
     *
     * @return A new message.
     */
    final public Message<?> getMessageSynch() {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageSynch(id);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    
    /**
     * Returns a message with a specific senderId waiting to be received by the specific agent. If there
     * is no  messages waiting the method blocks the agent excecution until a
     * new message is avaiable. If the agent is not registered to the
     * communication component (see reception()) the method returns null.
     *
     * @param senderId The sender of the message to search
     * @return A new message.
     */
    final public Message<?> getMessageSynch(Identificator senderId) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageSynch(id,senderId);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message with a specific senderId waiting to be received by the specific name agent. If there
     * is no  messages waiting the method blocks the agent excecution until a
     * new message is avaiable. If the agent is not registered to the
     * communication component (see reception()) the method returns null.
     *
     * @param senderName The name of the sender of the message to search
     * @return A new message.
     */
    final public Message<?> getMessageSynch(String senderName) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageSynch(id,senderName);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message with a specific senderId waiting to be received by the specific agent. If there
     * is no  messages waiting the method blocks the agent excecution until a
     * new message is avaiable. If the agent is not registered to the
     * communication component (see reception()) the method returns null.
     *
     * @param senderIds The array sender of the message to search
     * @return A new message.
     */
    final public Message<?> getMessageSynch(Identificator[] senderIds) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageSynch(id,senderIds);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message with a specific senderId waiting to be received by the specific name agent. If there
     * is no  messages waiting the method blocks the agent excecution until a
     * new message is avaiable. If the agent is not registered to the
     * communication component (see reception()) the method returns null.
     *
     * @param senderNames The array names of the sender of the message to search
     * @return A new message.
     */
    final public Message<?> getMessageSynch(String[] senderNames) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageSynch(id,senderNames);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }

    /**
     * Returns a message waiting to be received by the specific agent. If there
     * is no  messages waiting the method it returns null. If the agent is not
     * registered to the communication component (see reception()) the method
     * returns null.
     *
     * @return A new message.
     */
    final public Message<?> getMessageAsynch() {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageAsynch(id);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }

    /**
     * Returns a message waiting to be received by the specific agent. If there
     * is no  messages waiting the method it returns null. If the agent is not
     * registered to the communication component (see reception()) the method
     * returns null.
     * 
     * @param senderId The sender of the message to search
     *
     * @return A new message.
     */
    final public Message<?> getMessageAsynch(Identificator senderId) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageAsynch(id,senderId);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message waiting to be received by the specific agent. If there
     * is no  messages waiting the method it returns null. If the agent is not
     * registered to the communication component (see reception()) the method
     * returns null.
     * 
     * @param senderIds The array of identificators of agent senders of the message to search
     *
     * @return A new message.
     */
    final public Message<?> getMessageAsynch(Identificator[] senderIds) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageAsynch(id,senderIds);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message waiting to be received by the specific name agent. If there
     * is no  messages waiting the method it returns null. If the agent is not
     * registered to the communication component (see reception()) the method
     * returns null.
     * 
     * @param senderName The name of sender of the message to search
     *
     * @return A new message.
     */
    final public Message<?> getMessageAsynch(String senderName) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageAsynch(id,senderName);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Returns a message waiting to be received by one of the specific name agent. If there
     * is no  messages waiting the method it returns null. If the agent is not
     * registered to the communication component (see reception()) the method
     * returns null.
     * 
     * @param senderNames The array of sender names to search
     *
     * @return A new message.
     */
    final public Message<?> getMessageAsynch(String[] senderNames) {
        Message<?> tempMsg;

        if (basicServices.getReceiveInterface() != null) {
            if (basicServices.getReceiveInterface().isAgentRegistered(id)) {
                Message<?> message = basicServices.getReceiveInterface().getMessageAsynch(id,senderNames);
                tempMsg = message;
            } else {
                HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "Not registered into the CommunicationManager!");
                tempMsg = null;
            }
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + "No possibility to communicate!");
            tempMsg = null;
        }

        return tempMsg;
    }
    
    /**
     * Registers the agent to the communication component so a new message
     * queue is created. If the agent is already registered nothing changes.
     */
    final public void reception() {
        if (basicServices.getSendInterface() != null) {
            basicServices.getReceiveInterface().setAgentReception(id);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " MessageIO interface of the Communication component is null");
        }
    }

    /**
     * Erases the registration of the current agent in the communication
     * component. If the message queue is not empty nothing changes. So the
     * agent has to empty the message queue before.
     */
    final public void deReception() {
        if (basicServices.getReceiveInterface() != null) {
            basicServices.getReceiveInterface().removeAgentReception(id);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " MessageIO interface of the Communication component is null");
        }
    }

    /**
     * This method creates a new agent from a class.
     *
     * @param agentClass The class of the agent.
     *
     * @return The identificator of the new agent, or null if something goes wrong.
     */
    final public Identificator createAgent(Class <? extends Agent> agentClass) {
    	return createAgent(agentClass, agentClass.getName());
    }
    /**
     * This method creates a new agent from a class.
     *
     * @param agentClass The class of the agent.
     * @param aName The name of the new agent.
     *
     * @return The identificator of the new agent, or null if something goes wrong.
     */
    final public Identificator createAgent(Class<? extends Agent> agentClass, String aName) {
        Identificator tempId;

        if (basicServices.getGenesisInterface() != null) {
            tempId = basicServices.getGenesisInterface().createAgent(agentClass,aName);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Genesis interface of the Genesis component is null");
            tempId = null;
        }

        return tempId;
    }
    
    /**
     * This method creates a new agent from a class.
     * 
	 * @param agentClass
	 *            The class of the agent.
	 * @param objects
	 * 			  The parameters of the new agent
     *
     * @return The identificator of the new agent, or null if something goes wrong.
     */
    final public Identificator createAgent(Class<? extends Agent> agentClass, Object[] objects) {
        Identificator tempId;

        if (basicServices.getGenesisInterface() != null) {
            tempId = basicServices.getGenesisInterface().createAgent(agentClass, objects);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " Genesis interface of the Genesis component is null");
            tempId = null;
        }

        return tempId;
    }

    /**
     * This is the first method called when a new agent starts to run.
     */
    public abstract void init();

    /**
     * Returns the identificator of the current agent.
     *
     * @return The identificator of the current agent.
     */
    public final Identificator getIdentificator() {
        return id;
    }
    
    /**
     * Returns the agent name.
     *
     * @return The agent name.
     */
    final public String getAgentName() {
        return agentName;
    }

    /**
     * Given a key word this method searches if there are some services
     * currently running in the place and registered  to the discovery
     * component. It checks if the services has registered it self with the
     * same key word.
     *
     * @param keyWord The string to search in the discovery component.
     *
     * @return The identificator of the first service found with the key word
     *         given otherwise null.
     */
    final public Identificator getServiceIdentificator(String keyWord) {
        Identificator tempId;

        if (basicServices.getDiscoveryServiceAgentInterface() != null) {
            tempId = basicServices.getDiscoveryServiceAgentInterface().getServiceId(keyWord);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No Discovery Interface found!");
            tempId = null;
        }

        return tempId;
    }
    
    
    /**
     * Returns the protocol of the service agent
     * registered into the component.
     *
     * @param agentId The identificator of the service agent.
     *
     * @return The protocol associated or null.
     */
    final public AbstractProtocol getServiceProtocol(Identificator agentId) {
        AbstractProtocol tempProtocol;

        if (basicServices.getDiscoveryServiceAgentInterface() != null) {
            tempProtocol = basicServices.getDiscoveryServiceAgentInterface().getServiceProtocol(agentId);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No DiscoveryServiceAgents Interface found!");
            tempProtocol = null;
        }
        return tempProtocol;
    }
    
    /**
     * Returns the place address of a remote place.
     *
     * @param placeName The name of the remote place.
     *
     * @return The placeAddress of a remote place or null if not found.
     */
    final public PlaceAddress getPlaceAddressFromName(String placeName) {
        PlaceAddress tempPlaceAddress;

        if (basicServices.getDiscoveryPlacesInterface() != null) {
            tempPlaceAddress = basicServices.getDiscoveryPlacesInterface().getPlaceAddressFromPlaceName(placeName);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No DiscoveryPlaces Interface found!");
            tempPlaceAddress = null;
        }
        return tempPlaceAddress;
    }

    /**
     * Given an array of key words this method searches if there are some
     * services currently running in the place and registered  to the
     * discovery component. It checks if the services has registered it self
     * with the same key words.
     *
     * @param keyWordsArray The array of key words to search in the discovery
     *        component.
     *
     * @return The identificator of the first service found with the array of
     *         key words given otherwise null.
     */
    final public Identificator getServiceIdentificator(String[] keyWordsArray) {
        Identificator tempId;

        if (basicServices.getDiscoveryServiceAgentInterface() != null) {
            tempId = basicServices.getDiscoveryServiceAgentInterface().getServiceId(keyWordsArray);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No Discovery Interface found!");
            tempId = null;
        }

        return tempId;
    }

    /**
     * Login to the service
     *
     * @param serviceAgentIdentificator The service agent identificator
     * @param userdId user
     * @param passwd password
     *
     * @return The result true or false.
     */
    final public boolean loginToService(Identificator serviceAgentIdentificator, String userdId, String passwd) {
        boolean success;

        if (basicServices.getSecurityInterface() != null) {
            success = basicServices.getSecurityInterface().loginAgent(this.getIdentificator(), serviceAgentIdentificator, userdId, passwd);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No Security Interface found!");
            success = false;
        }

        return success;
    }

    /**
     * Logout to Service
     *
     * @param serviceAgentIdentificator The service agent identificator.
     *
     * @return The result true or false.
     */
    final public boolean logoutToService(Identificator serviceAgentIdentificator) {
        boolean success;

        if (basicServices.getSecurityInterface() != null) {
            success = basicServices.getSecurityInterface().logoutAgent(this.getIdentificator(), serviceAgentIdentificator);
        } else {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No Security Interface found!");
            success = false;
        }

        return success;
    }

    /**
     * This method is called automatically when an agent is going to dead. It
     * erases all the entries in the current place and in the born place.
     */
    final public void dead() {
        if (basicServices.getGenesisInterface() == null) {
            HermesLogger.log(HermesLogger.INFO, "Agent:" + id + " No genesis component the dead is unclean!");
        } else {
            //I have to dereception and to empty the message queue.
            deReception();

            if (basicServices.getSecurityInterface() != null) {
                if (this.id.isServiceAgent()) {
                    basicServices.getSecurityInterface().unregisterServiceAgent(this.id);
                }
                basicServices.getSecurityInterface().logoutAgent(this.id);
            }

            basicServices.getGenesisInterface().destroyAgent(this.id);
        }
    }
}
